# _*_ coding utf-8 _*_
# FILENAME：.py
# DESCRIPTION:
# AUTH:
# DATE: 2022/6/82:45 下午

import time
import traceback
import logging
import datetime

import pandas as pd

from algorithm.common import CommonUtils
from algorithm.orderPlan import algorithm_methods

from constant import CacheType
from models.baseModel import get_session
from models.requireModel import RequireModel
from utils.cacheUtils import Cache


class CommentsUtils(CommonUtils):
    def get_welding_assembly(self, id):
        """计算焊装/总装零件需求
        """
        ## 数据初始化 begin
        init_start_time = int(time.time())

        # 当天0点
        now = datetime.datetime.now()
        schedule_date = now - datetime.timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond)

        session = get_session()
        require = session.query(RequireModel).filter(RequireModel.id == id).first()
        session.close()

        # 工厂ID
        factory = require.factory_id

        # BOM数据
        cache = Cache(CacheType.BOM.value, require.bom_version_id)
        bom_df = pd.read_csv(cache.get_cache())
        # 生产速度
        cache = Cache(CacheType.PRODUCE_SPEED.value, require.produce_speed_version_id)
        jph_df = pd.read_csv(cache.get_cache())
        # 生产队列
        cache = Cache(CacheType.PRODUCE_QUEUE.value, require.produce_queue_version_id)
        mes_df = pd.read_csv(cache.get_cache())
        # 零件基础信息
        cache = Cache(CacheType.PARTS.value, require.parts_version_id)
        component_base_df = pd.read_csv(cache.get_cache())
        # 零件装配延误时间
        cache = Cache(CacheType.PARTS_LOAD_DELAY_TIME.value, require.load_delay_time_version_id)
        component_delay_df = pd.read_csv(cache.get_cache())

        car_part_df = CommonUtils.get_car_part_df(component_base_df, component_delay_df)
        if car_part_df.empty:
            raise Exception('请检查零件装配延误时间表和零件基础信息表！')

        car_df = CommonUtils.make_car_df(bom_df, car_part_df)
        if car_df.empty:
            raise Exception('零件信息相关表无法与BOM表匹配！')

        init_end_time = int(time.time())
        ## 数据初始化 end

        ## 运算 begin
        cal_start_time = int(time.time())

        try:
            is_parallel = True if require.production_line == 1 and (factory == 1 or factory == 3) else False
            jph_time_df = self.__jph_time(factory, jph_df, bom_df, mes_df, schedule_date, is_parallel=is_parallel)
            if jph_time_df.empty:
                raise Exception('请检查是否设置生产时间，生产队列表或生产速度表是否正确！')
            latest_arrival_time_df = find_latest_arrival_times(jph_time_df, car_df, car_part_df,
                                                                          schedule_date,
                                                                          self.end_product_time)
        except Exception as e:
            raise Exception(e)

        latest_arrival_time_records = latest_arrival_time_df.to_dict(orient="records")

        cal_end_time = int(time.time())
        ## 运算 end

        return latest_arrival_time_records, init_end_time - init_start_time, cal_end_time - cal_start_time

    def get_coating(self, id):
        """计算涂装零件需求
        """
        # lms_df = pd.DataFrame(lms_table)
        # latest_arrival_time_df = self.__format_lms(lms_df, schedule_date, factory)
        # if latest_arrival_time_df.empty:
        #     raise Exception('LMS表无当日零件调度需求！')
        #
        # latest_arrival_time_records = latest_arrival_time_df.to_dict(orient="records")
        # return latest_arrival_time_records

    def __format_lms(self, lms_df: pd.DataFrame, schedule_date: datetime, factory: int) -> pd.DataFrame:
        # 根据schedule_date过滤lms_df 数据
        lms_df = lms_df.set_index('最新要求到货时间', drop=False)
        schedule_date_str = schedule_date.strftime("%Y-%m-%d")
        lms_df = lms_df[schedule_date_str]

        # TODO 获取关联排班
        result_time = self.find_latest_active_worktime(factory, "短白班")

        # 默认开工时间为 8:00
        start_time = 8
        end_time = 18
        if result_time:
            start_time_str = result_time["work_time"]["up_time"][0]["start"]
            start_time = int(start_time_str.split(":")[0])
            end_time_str = result_time["work_time"]["up_time"][0]["end"]
            end_time = int(end_time_str.split(":")[0])

        # 读取mes中所有的车型
        start_product_time = schedule_date + datetime.timedelta(hours=start_time)
        end_product_time = schedule_date + datetime.timedelta(hours=end_time)

        latest_arrival_time_records = []
        for row in lms_df.itertuples():
            tmp_item = {
                "零件名称": getattr(row, '零件名称'),
                "零件件号": getattr(row, '零件'),
                "供应商名称": getattr(row, '供应商名称'),
                "数量": getattr(row, '数量'),
            }
            # 考虑工厂实际生产时间，所有下班时间之外的零件，只要是当天的，都移到下班之前，上班时间之内的错开休息时间即可（提前）
            tmp_arrival_time = getattr(row, '最新要求到货时间')
            if tmp_arrival_time >= end_product_time:
                tmp_item["到货时间"] = end_product_time
            elif tmp_arrival_time <= start_product_time:
                tmp_item["到货时间"] = start_product_time
            else:
                tmp_item["到货时间"] = tmp_arrival_time
            latest_arrival_time_records.append(tmp_item)

        latest_arrival_time_df = pd.DataFrame(latest_arrival_time_records)
        if latest_arrival_time_df.empty:
            return pd.DataFrame([])
        latest_arrival_time_df = latest_arrival_time_df.sort_values(by=['到货时间'])
        return latest_arrival_time_df

    def __jph_time(self, factory, Jph_df, Bom_df, MES_df, schedule_date, is_parallel=False):
        """
        - bom_id : BOM表
         - jph_id: 生产速度-总装(JPH)
         - mes_id: 生产队列数据-总装
         - component_base_id :零件基础信息-总装
         - component_delay_id :零件装配延误时间-总装
        """

        # TODO 获取关联排班
        result_time = self.find_latest_active_worktime(factory, "短白班")

        # 默认开工时间为 8:00
        start_time = 8
        end_time = 18
        if result_time:
            start_time_str = result_time["work_time"]["up_time"][0]["start"]
            start_time = int(start_time_str.split(":")[0])
            end_time_str = result_time["work_time"]["up_time"][0]["end"]
            end_time = int(end_time_str.split(":")[0])
        # 读取mes中所有的车型
        mes_car_types_list = list(set(MES_df["car_model"].values.tolist()))
        start_product_time = schedule_date + datetime.timedelta(hours=start_time)
        end_product_time = schedule_date + datetime.timedelta(hours=end_time)
        # 匹配BOM表和MES（生产队列）表
        bom_car_ids = Bom_df["car_model"].apply(lambda x: x.split(".")[0])
        bom_car_ids = list(set(bom_car_ids.values.tolist()))

        self.end_product_time = end_product_time
        self.start_product_time = start_product_time

        new_input_counts = []
        for key in mes_car_types_list:
            if key not in bom_car_ids:
                pass
            else:
                new_input_counts.append(key)

        logging.info(f"jph table content: {Jph_df}")
        logging.info(f"current new input counts: {new_input_counts}")

        jph_time_list = []
        # 根据生产队列和JPH 得到时间标签
        last_car_end_time = start_product_time
        _start_product_time = last_car_end_time
        _end_product_time = end_product_time
        is_first = True
        for _car_id in new_input_counts:
            # 设置筛选条件 => 筛选生产速度表中对应车型，便于获取生产速度
            mask = (Jph_df["car_model"] == _car_id)
            filter_result = Jph_df.loc[mask]
            if filter_result.empty:
                logging.info(f"car_id({_car_id}) not in jph list, continue...")
                continue

            _jph_value = filter_result["produce_speed"].values.tolist()[0]

            # 根据Car ID筛选出当前生产队列中车型数据
            mes_filter_result = MES_df[MES_df["car_model"] == _car_id]
            car_count = 0
            # 并行条件
            if is_parallel:
                last_car_end_time = start_product_time
            for _row in mes_filter_result.itertuples():
                _start_product_time = last_car_end_time
                # 串行条件
                if not is_parallel:
                    car_count = car_count + 1 if not is_first and car_count == 0 else car_count
                delta = datetime.timedelta(minutes=int(60 / _jph_value) * car_count)
                _start_product_time += delta

                # 当零件需求时间超过当天，退出循环
                if _start_product_time.date() != start_product_time.date():
                    break

                _name = f"{getattr(_row, 'car_model')}_{car_count}"
                _car_type = f"{getattr(_row, 'car_model')}{getattr(_row, 'optional_equipment')}"
                car_count += 1

                # 考虑工厂实际生产时间，所有下班时间之外的零件，只要是当天的，都移到下班之前，上班时间之内的错开休息时间即可（提前）
                tmp_car_part_require = {
                    "Name": _name,
                    "Car_type": _car_type,
                    "JPH": _jph_value
                }
                if _start_product_time >= _end_product_time:
                    tmp_car_part_require["Latest_arrival_time"] = _end_product_time
                else:
                    tmp_car_part_require["Latest_arrival_time"] = _start_product_time
                jph_time_list.append(tmp_car_part_require)

                is_first = False
            last_car_end_time = _start_product_time
            logging.info("----------------------------")
            logging.info(f"car_id: {_car_id}, count: {car_count}")
            logging.info("----------------------------")

        logging.info(f"jph list length: {len(jph_time_list)}")

        jph_time_df = pd.DataFrame(jph_time_list)
        jph_time_df = jph_time_df.sort_values(by=['Latest_arrival_time'])
        return jph_time_df

# 根据生产队列和JPH来计算零件需求
def find_latest_arrival_times(jph_time_df, car_df, car_part_df, schedule_date, _end_product_time):
    car_part_names = []
    car_part_ids = []
    latest_arrival_times = []
    quantities = []
    supplier_names = []
    delivery_crossings = []
    packing_constraints = []
    packing_types = []
    car_type = []
    car_part_cache = {}

    algorithm_tools = algorithm_methods(car_df=car_df, car_part_df=car_part_df)
    for idx, data in jph_time_df.iterrows():
        _car_type = jph_time_df.at[idx, 'Car_type']
        _jph = jph_time_df.at[idx, 'JPH']
        latest_arrival_time = jph_time_df.at[idx, 'Latest_arrival_time']

        cars_batch = {
            _car_type: _jph,
        }

        # # Find parts required
        # car_part_df = pd.DataFrame()

        # 缓存结果避免重复计算
        if _car_type in car_part_cache:
            products_toBuy_dic = car_part_cache[_car_type]
        else:
            products_toBuy_dic = algorithm_tools.all_car_parts(cars_batch.copy())
            car_part_cache[_car_type] = products_toBuy_dic

        # Append car parts, arrival times, etc
        for car_part in products_toBuy_dic:
            delta = datetime.timedelta(minutes=products_toBuy_dic[car_part][7])  # 追加对应零件装配延误时间

            _latest_arrival_time = latest_arrival_time + delta

            if _latest_arrival_time.date() != schedule_date.date():
                logging.warning(
                    f"latest arrival time: {_latest_arrival_time}, schedule date: {schedule_date}")
                break

            if _latest_arrival_time >= _end_product_time:
                latest_arrival_times.append(_end_product_time)
            else:
                latest_arrival_times.append(_latest_arrival_time)
            car_part_names.append(products_toBuy_dic[car_part][8])
            car_part_ids.append(car_part)
            quantities.append(products_toBuy_dic[car_part][0])
            supplier_names.append(products_toBuy_dic[car_part][4])
            delivery_crossings.append(products_toBuy_dic[car_part][3])
            packing_types.append(products_toBuy_dic[car_part][2])
            packing_constraints.append(products_toBuy_dic[car_part][6])
            car_type.append(_car_type)

    arrival_times_df = pd.DataFrame(
        {"工厂": "", "零件名称": car_part_names, "生产线": "", "零件件号": car_part_ids, "到货时间": latest_arrival_times,
         "数量": quantities, "供应商名称": supplier_names, "生产车型": car_type})
    arrival_times_df = arrival_times_df.sort_values(by="到货时间")
    return arrival_times_df.reset_index(drop=True)


